﻿/*
   Copyright 2012 University of Southampton

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using System.Data;
using System.Xml;
using uk.ac.soton.ses.DAL;

namespace uk.ac.soton.ses
{
    /// <summary>
    /// Wrapper class for managing HDCConfigurationOptions
    /// </summary>
    public class HDCConfiguration
    {
        /// <summary>
        /// A shared static string which defines the first part of URLs in the HDC
        /// </summary>
        public static readonly string Url_BasePath = "/_layouts/HDC";

        /// <summary>
        /// A shared static string which defines the location of an HDC web page within SharePoint
        /// </summary>
        public static readonly string Url_ExperimentDetails = Url_BasePath + "/ExperimentDetails.aspx";

        /// <summary>
        /// A shared static string which defines the location of an HDC web page within SharePoint
        /// </summary>
        public static readonly string Url_CollectionDetails = Url_BasePath + "/CollectionDetails.aspx";

        /// <summary>
        /// A shared static string which defines the location of an HDC web page within SharePoint
        /// </summary>
        public static readonly string Url_DelimitedFileViewer = Url_BasePath + "/DelimitedFileViewer.aspx";

        /// <summary>
        /// A shared static string which defines the location of an HDC web page within SharePoint
        /// </summary>
        public static readonly string Url_StatsFileViewer = Url_BasePath + "/StatsFileViewer.aspx";

        /// <summary>
        /// A shared static string which defines the location of an HDC web page within SharePoint
        /// </summary>
        public static readonly string Url_ReportPrintout = Url_BasePath + "/ReportPrintout.aspx";

        /// <summary>
        /// A shared static string which defines the location of an HDC web page within SharePoint
        /// </summary>
        public static readonly string Url_ReportSelection = Url_BasePath + "/ReportSelection.aspx";

        /// <summary>
        /// A shared static string which defines the location of an HDC web page within SharePoint
        /// </summary>
        public static readonly string Url_ParameterGraphCategory = Url_BasePath + "/ParameterGraphCategory.aspx";

        /// <summary>
        /// A shared static string which defines the location of an HDC web page within SharePoint
        /// </summary>
        public static readonly string Url_ParameterGraphXY = Url_BasePath + "/ParameterGraphXY.aspx";

        /// <summary>
        /// A shared static string which defines the location of an HDC web page within SharePoint
        /// </summary>
        public static readonly string Url_FileGraphXY = Url_BasePath + "/FileGraphXY.aspx";

        /// <summary>
        /// A shared static string which defines the location of an HDC web page within SharePoint
        /// </summary>
        public static readonly string Url_CTDataViewer = Url_BasePath + "/CTDataViewer.aspx";

        /// <summary>
        /// A shared static string which defines the location of an HDC web page within SharePoint
        /// </summary>
        public static readonly string Url_BasicImageHandler = Url_BasePath + "/BasicImageHandler.ashx";

        //Fields
        private HDCConfigurationOptions _configurationOptions = null;
        private string _configFilePath = "";
        private static readonly object _configInstanceLock = new object();
        private static HDCConfiguration _configInstance = null;

        //Properties

        /// <summary>
        /// The configuration options for this HDC instance, loaded from the database with a configuration file storing the information required to locate the database and the configuration record
        /// </summary>
        public HDCConfigurationOptions ConfigurationOptions { get { return this._configurationOptions; } set { this._configurationOptions = value; } }

        /// <summary>
        /// The path to the configuration file used
        /// </summary>
        public string ConfigFilePath { get { return _configFilePath; } set { _configFilePath = value; } }

        /// <summary>
        /// A single instance shared version of the configuration class so the configuration does not require repeated loading
        /// </summary>
        public static HDCConfiguration Instance
        {
            get
            {
                //Only one initialisation permitted at once
                lock (_configInstanceLock)
                {
                    if (_configInstance == null)
                    {
                        //Load the database connection string from the config file
                        _configInstance = new HDCConfiguration();
                    }
                    return _configInstance;
                }
            }
        }

        /// <summary>
        /// Constructor that loads the settings from the .xml file specified in the app.config file
        /// Use DatabaseConnectionValid() to verify the connection string stored in the config file
        /// </summary>
        public HDCConfiguration()
        {
            //Lookup the default name and path of the config file
            string defaultConfigPath = HDCConfigurationOptionsProperties.Default.ConfigPath;
            string defaultConfigFileName = HDCConfigurationOptionsProperties.Default.ConfigFileName;
            string defaultConfigFilePath = Path.Combine(defaultConfigPath, defaultConfigFileName);

            //Configure the options from the config file, and database if possible
            this.InitialiseSettings(defaultConfigFilePath);
        }

        /// <summary>
        /// Constructor that loads the settings from the .xml file specified in the passed parameter
        /// Use DatabaseConnectionValid() to verify the connection string stored in the config file
        /// </summary>
        /// <param name="configFilePath">Path to the .xml containing database connection information</param>
        public HDCConfiguration(string configFilePath)
        {
            this.InitialiseSettings(configFilePath);
        }

        /// <summary>
        /// Initialises the settings by loading the database information from an .xml file and then loading the remaining options from a database
        /// Use DatabaseConnectionValid() to verify the connection string
        /// </summary>
        /// <param name="configFilePath">The path to the config file</param>
        private void InitialiseSettings(string configFilePath)
        {
            this._configFilePath = configFilePath;

            //If the file exists we'll try and use it, otherwise we'll initialise with empty settings
            if (File.Exists(this._configFilePath))
            {
                //Get the database settings and configuration container IDs from config file
                this._configurationOptions = HDCConfiguration.LoadDatabaseSettingsFromFile(this._configFilePath);

                try
                {
                    //Populate the options from the database containers
                    using (MaterialsMetadataContext dbcontext = new MaterialsMetadataContext(this._configurationOptions.DatabaseOptions.EntityConnectionString))
                    {
                        //Ensure we have the right type of containers
                        if (this.ConfigurationContainersValid())
                        {
                            this._configurationOptions.SharePointOptions.LoadConfigurationFromDatabase(dbcontext);
                            this._configurationOptions.FileSystemWatcherOptions.LoadConfigurationFromDatabase(dbcontext);
                        }
                    }
                }
                //Connection problem, but we still need a configuration object so we will catch this exception to ensure we still get objects that can be edited
                catch (EntityException)
                {
                    //Do nothing, we have SharePointOptions and FileSystemWatcherOptions objects serialised from disk,
                    //so leave them and let the caller read the settings (or check the connection by calling DatabaseConnectionValid())
                }
            }
            else
            {
                //Setup default instances of each settings class
                //Create default instances of each settings class to save in the file with passed name
                this.ClearAllSettings();
            }
        }

        /// <summary>
        /// Checks whether the configured connection string is valid
        /// </summary>
        /// <returns>Whether the connection string creates a MaterialsMetadataContext object</returns>
        public bool DatabaseConnectionValid()
        {
            bool isValid = true;

            using (MaterialsMetadataContext dbcontext = new MaterialsMetadataContext(this.ConfigurationOptions.DatabaseOptions.EntityConnectionString))
            {
                try
                {
                    dbcontext.Connection.Open();
                }
                catch (EntityException)
                {
                    isValid = false;
                }
            }

            return isValid;
        }

        /// <summary>
        /// Checks the configuration containers specified in the configuration file are of the right type.
        /// </summary>
        /// <returns></returns>
        public bool ConfigurationContainersValid()
        {
            bool isValid = true;
            using (MaterialsMetadataContext dbcontext = new MaterialsMetadataContext(this.ConfigurationOptions.DatabaseOptions.EntityConnectionString))
            {
                Experiment filesystemwatcherContainer = dbcontext.Experiments.Include("ExperimentType").FirstOrDefault(exp => exp.ID == this.ConfigurationOptions.FileSystemWatcherOptions.ConfigurationContainerID);
                Experiment sharepointContainer = dbcontext.Experiments.Include("ExperimentType").FirstOrDefault(exp => exp.ID == this.ConfigurationOptions.SharePointOptions.ConfigurationContainerID);
                if (filesystemwatcherContainer == null)
                {
                    isValid = false;
                }
                else if (filesystemwatcherContainer.ExperimentType.ID != (int)HDCExperimentTypes.FileStoreOptions)
                {
                    isValid = false;
                }
                if (sharepointContainer == null)
                {
                    isValid = false;
                }
                else if (sharepointContainer.ExperimentType.ID != (int)HDCExperimentTypes.SharePointInterfaceOptions)
                {
                    isValid = false;
                }
            }
            return isValid;
        }

        /// <summary>
        /// Given an XML file, this method will load the database settings required to connect to the database to load the remaining settings
        /// </summary>
        /// <param name="filePath">The path to the XML file, of serialised type HDCConfigurationOptions</param>
        public static HDCConfigurationOptions LoadDatabaseSettingsFromFile(string filePath)
        {
            //This is static because we load the settings from a configfile, but we don't store the name of the config file or make any changes to the state of the class

            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException("Configuration file could not be found", filePath);
            }

            using (FileStream fs = File.OpenRead(filePath))
            {
                //Deserialise the settings from the XML file into a new instance of the HDCConfigurationOptions class
                XmlSerializer xmlSerializer = new XmlSerializer(typeof(HDCConfigurationOptions));
                try
                {
                    HDCConfigurationOptions loadedOptions = (HDCConfigurationOptions)xmlSerializer.Deserialize(fs);
                    return loadedOptions;
                }
                catch (InvalidOperationException e)
                {
                    if (e.InnerException is XmlException)
                    {
                        //We know that the file exists but it could not be parsed. We won't do anything and let the caller handle whether to fix the corruption.
                        throw;
                    }
                    else
                    {
                        //We don't know how to handle this, so we will rethrow the exception
                        throw;
                    }
                }
            }
            //We should have whatever was configured as a database name, server, and configuration container IDs now
        }

        /// <summary>
        /// Sets all the configuration to their default values 
        /// </summary>
        public void ClearAllSettings()
        {
            //Create a new blank options class
            this._configurationOptions = new HDCConfigurationOptions();
        }

        /// <summary>
        /// Save the database connection settings to the .xml file and the SharePoint and File Store options to the database
        /// </summary>
        /// <param name="overwrite">Whether to overwrite the config file</param>
        public void SaveChanges(bool overwrite)
        {
            this.SaveChangesToFile(overwrite);
            this.SaveChangesToDatabase();
        }


        /// <summary>
        /// Save the database connection settings to the .xml file
        /// </summary>
        /// <param name="overwrite">Whether to overwrite the config file</param>
        public void SaveChangesToFile(bool overwrite)
        {
            //Save the database connection settings to a file
            using (FileStream fs = new FileStream(this._configFilePath, overwrite ? FileMode.Create : FileMode.CreateNew))
            using (XmlTextWriter xmlwriter = new XmlTextWriter(fs, Encoding.UTF8))
            {
                xmlwriter.Formatting = Formatting.Indented;
                XmlSerializer xmlSerializer = new XmlSerializer(typeof(HDCConfigurationOptions));
                xmlSerializer.Serialize(xmlwriter, this._configurationOptions);
            }
        }

        /// <summary>
        /// Save the SharePoint and File Store options to the database
        /// </summary>
        public void SaveChangesToDatabase()
        {
            //Save the remaining settings to the database
            using (MaterialsMetadataContext dbcontext = new MaterialsMetadataContext(this._configurationOptions.DatabaseOptions.EntityConnectionString))
            {
                this._configurationOptions.FileSystemWatcherOptions.SaveConfigurationToDatabase(dbcontext);
                this._configurationOptions.SharePointOptions.SaveConfigurationToDatabase(dbcontext);
            }
        }

        /// <summary>
        /// Creates a new SharePoint configuration container and reloads the configuration
        /// </summary>
        public void CreateNewFileSystemWatcherContainer()
        {
            using (MaterialsMetadataContext dbcontext = new MaterialsMetadataContext(this._configurationOptions.DatabaseOptions.EntityConnectionString))
            {
                Experiment newConfig = this._configurationOptions.FileSystemWatcherOptions.CreateNewConfigurationContainer(dbcontext);
                this._configurationOptions.FileSystemWatcherOptions.ConfigurationContainerID = newConfig.ID;
                this._configurationOptions.FileSystemWatcherOptions.LoadConfigurationFromDatabase(dbcontext);
            }
        }

        /// <summary>
        /// Creates a new SharePoint configuration container and reloads the configuration
        /// </summary>
        public void CreateNewSharePointContainer()
        {
            using (MaterialsMetadataContext dbcontext = new MaterialsMetadataContext(this._configurationOptions.DatabaseOptions.EntityConnectionString))
            {
                Experiment newConfig = this._configurationOptions.SharePointOptions.CreateNewConfigurationContainer(dbcontext);
                this._configurationOptions.SharePointOptions.ConfigurationContainerID = newConfig.ID;
                this._configurationOptions.SharePointOptions.LoadConfigurationFromDatabase(dbcontext);
            }
        }

        /// <summary>
        /// Returns the current configuration using the default location for the database configuration file
        /// </summary>
        /// <returns>An HDCConfigurationOptions class with the current configuration</returns>
        public static HDCConfigurationOptions GetConfigurationOptions()
        {
            return Instance.ConfigurationOptions;
        }

        /// <summary>
        /// Creates a URL for the data set's web page, based on the system's configuration and other settings.
        /// </summary>
        /// <param name="experimentID">The ID of the data set</param>
        /// <returns>A string containing the URL.</returns>
        public static string GetUrl_ExperimentDetails(int experimentID)
        {
            
            return string.Format("{0}?ID={1}", HDCConfiguration.Url_ExperimentDetails, experimentID);
        }

        /// <summary>
        /// Creates a URL pointing to the data set collection's web page.
        /// </summary>
        /// <param name="collectionID">The ID of the collection.</param>
        /// <returns>A string containing the URL.</returns>
        public static string GetUrl_CollectionDetails(int collectionID)
        {
            return string.Format("{0}?CollectionID={1}", HDCConfiguration.Url_CollectionDetails, collectionID);
        }

        /// <summary>
        /// Creates a URL pointing to the delimited file viewer web page for a specific data file.
        /// </summary>
        /// <param name="dataFileID">The ID of the data file.</param>
        /// <returns>A string containing the URL.</returns>
        public static string GetUrl_DelimitedFileViewer(int dataFileID)
        {
            return string.Format("{0}?DataFileID={1}", HDCConfiguration.Url_DelimitedFileViewer, dataFileID);
        }

        /// <summary>
        /// Creates a URL pointing to the stats file viewer web page for a specific data file.
        /// </summary>
        /// <param name="dataFileID">The ID of the data file.</param>
        /// <returns>A string containing the URL.</returns>
        public static string GetUrl_StatsFileViewer(int dataFileID)
        {
            return string.Format("{0}?DataFileID={1}", HDCConfiguration.Url_StatsFileViewer, dataFileID);
        }

        /// <summary>
        /// Creates a URL pointing to the CT data file viewer web page for a specific data file.
        /// </summary>
        /// <param name="dataFileID">The ID of the data file.</param>
        /// <returns>A string containing the URL.</returns>
        public static string GetUrl_CTDataViewer(int dataFileID)
        {
            return string.Format("{0}?DataFileID={1}", HDCConfiguration.Url_CTDataViewer, dataFileID);
        }

        /// <summary>
        /// Creates a URL pointing to the report printout web page for a specific data set.
        /// </summary>
        /// <param name="datasetID">The ID of the data set.</param>
        /// <returns>A string containing the URL.</returns>
        public static string GetUrl_ReportPrintout(int datasetID)
        {
            return string.Format("{0}?ExperimentID={1}", HDCConfiguration.Url_ReportPrintout, datasetID);
        }

        /// <summary>
        /// Creates a URL pointing to the advanced report printout web page (for a specific data set) that allows selection of parameters and files
        /// </summary>
        /// <param name="datasetID">The ID of the data set.</param>
        /// <returns>A string containing the URL.</returns>
        public static string GetUrl_ReportSelection(int datasetID)
        {
            return string.Format("{0}?ExperimentID={1}", HDCConfiguration.Url_ReportSelection, datasetID);
        }

        /// <summary>
        /// Creates a URL pointing to the parameter graphing by category web page for a specific data set.
        /// </summary>
        /// <param name="datasetID">The ID of the data set.</param>
        /// <returns>A string containing the URL.</returns>
        public static string GetUrl_ParameterGraphCategory(int datasetID)
        {
            return string.Format("{0}?ExperimentID={1}", HDCConfiguration.Url_ParameterGraphCategory, datasetID);
        }

        /// <summary>
        /// Creates a URL pointing to the X vs Y parameter graphing web page for a specific data set.
        /// </summary>
        /// <param name="datasetID">The ID of the data set.</param>
        /// <returns>A string containing the URL.</returns>
        public static string GetUrl_ParameterGraphXY(int datasetID)
        {
            return string.Format("{0}?ExperimentID={1}", HDCConfiguration.Url_ParameterGraphXY, datasetID);
        }

        /// <summary>
        /// Creates a URL pointing to the X vs Y data file graphing web page for a specific data file and parse settings.
        /// </summary>
        /// <param name="dataFileID">The ID of the data file.</param>
        /// <param name="delim">The delimiter used in the file.</param>
        /// <param name="startrow">The line to start processing data at.</param>
        /// <param name="headerrow">The line in the file to use as the header row (if 0 or less, header row not present)</param>
        /// <param name="maxrows">The maximum number of lines to process</param>
        /// <returns>A string containing the URL</returns>
        public static string GetUrl_FileGraphXY(int dataFileID, char delim, int startrow, int headerrow, int maxrows)
        {
            return string.Format("{0}?DataFileID={1}&StartRow={1}:{2}&MaxRows={1}:{3}&Delimiter={1}:{4}&HeaderRow={1}:{5}", HDCConfiguration.Url_FileGraphXY, dataFileID, startrow, maxrows, delim, headerrow);
        }

        /// <summary>
        /// Creates a URL pointing to the X vs Y data file graphing web pages for a specific data file without configuring parse settings.
        /// </summary>
        /// <param name="dataSetID">The ID of the data file.</param>
        /// <returns>A string containing the URL.</returns>
        public static string GetUrl_FileGraphXY(int dataSetID)
        {
            return string.Format("{0}?ExperimentID={1}", HDCConfiguration.Url_FileGraphXY, dataSetID);
        }

        /// <summary>
        /// Creates a URL pointing to the image handler web page for a specific data file which creates an image from a data file, converting it if possible.
        /// </summary>
        /// <param name="dataFileID">The ID of the data file.</param>
        /// <param name="fileFormat">The file format to use, e.g. 'jpeg', 'jpg', 'png'.</param>
        /// <returns>A string containg the URL.</returns>
        public static string GetUrl_BasicImageHandler(int dataFileID, string fileFormat)
        {
            return string.Format("{0}?experimentdatafileid={1}&fileformat={2}", HDCConfiguration.Url_BasicImageHandler, dataFileID, fileFormat);
        }

    }
}
